package com.yonyou.pmclouds.plan.rmiimpl;

import com.alibaba.dubbo.config.annotation.Service;
import com.yonyou.pmclouds.ApplicationConsts;
import com.yonyou.pmclouds.basecom.consts.PlanVersionCompareConst;
import com.yonyou.pmclouds.basecom.exception.BusinessException;
import com.yonyou.pmclouds.businessobject.util.SortListUtil;
import com.yonyou.pmclouds.plan.entity.ScheduleItemVO;
import com.yonyou.pmclouds.plan.entity.ScheduleVO;
import com.yonyou.pmclouds.plan.rmiitf.IPlanVersionWebService;
import com.yonyou.pmclouds.plan.utils.CompareConfigurationUtil;
import com.yonyou.pmclouds.plan.utils.compare.PlanVersion;
import com.yonyou.pmclouds.plan.utils.compare.Progress;
import com.yonyou.pmclouds.plan.utils.compare.Property;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.time.DateUtils;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.util.*;

@Slf4j
@Service(interfaceClass = IPlanVersionWebService.class, version = ApplicationConsts.APPLICATION_VERSION, timeout = 10000)
public class PlanVersionWebServiceImpl implements IPlanVersionWebService {

    @Override
    public ScheduleVO compare(List<ScheduleVO> scheduleVOS,String pkSchedule,String preScheduleId) throws BusinessException {

        if(null != scheduleVOS && scheduleVOS.size() == PlanVersionCompareConst.PlAN_AMOUNT){

            Map<String,ScheduleItemVO[]> allVersionScheduleItemVOs = new HashMap<>();
            Map<String,ScheduleVO> allVersionScheduleVOs = new HashMap<>();

            for(ScheduleVO scheduleVO : scheduleVOS){
                allVersionScheduleItemVOs.put(scheduleVO.getPkSchedule(),scheduleVO.getScheduleItemVOS());
                allVersionScheduleVOs.put(scheduleVO.getPkSchedule(),scheduleVO);
            }

            ScheduleItemVO[] currentVersionVOArray = allVersionScheduleItemVOs.get(pkSchedule);
            ScheduleItemVO[] preVersionVOArray = allVersionScheduleItemVOs.get(preScheduleId);

            //当前版本所有子节点的集合（这一步是要把层叠的树结构变成平级的节点集合）
            List<ScheduleItemVO> currentVersionVOs = new ArrayList<>();
            //上个版本所有子节点的集合
            List<ScheduleItemVO> preVersionVOs = new ArrayList<>();

            //获取两个版本的排重后的唯一位(pkOriginals)
            Set<String> pkOriginals = new HashSet<>();

            //当前版本子节点容器 key：pk_original, value : scheduleItemVO (当前版本子节点VO)
            Map<String,ScheduleItemVO> currentVersionMap = new HashMap<>();
            //上个版本子节点容器 key：pk_original, value : scheduleItemVO (上个版本子节点VO)
            Map<String,ScheduleItemVO> preVersionMap = new HashMap<>();

            //单独处理删除节点的容器 key:pkScheduleItem value:scheduleItemVO(注：上个版本的VO)
            Map<String,ScheduleItemVO> preVersionDeleteProcessMap = new HashMap<>();

            //递归初始化以上准备好的容器
            parseAllSubNodes(currentVersionVOArray,currentVersionVOs,pkOriginals,currentVersionMap,null);//当前版本
            parseAllSubNodes(preVersionVOArray,preVersionVOs,pkOriginals,preVersionMap,preVersionDeleteProcessMap);//上个版本

            //比较处理
            compareProcess(currentVersionVOs, pkOriginals, currentVersionMap, preVersionMap, preVersionDeleteProcessMap);

            //比较完成后重新组合成树结构
            ScheduleVO scheduleVO = reBuildTree(pkSchedule, allVersionScheduleVOs, currentVersionVOs);

            //返回当前版本经过比较后的树结构
            return scheduleVO;
        }else{
            //防止直接调用后台业务接口直接传输参数
            throw new BusinessException("版本数量获取错误,无法对比");
        }
    }

    private ScheduleVO reBuildTree(String pkSchedule, Map<String, ScheduleVO> allVersionScheduleVOs, List<ScheduleItemVO> currentVersionVOs) {
        Map<String, List<ScheduleItemVO>> scheduleTreeVO = getScheduleTreeVO(currentVersionVOs);

        ScheduleVO scheduleVO = allVersionScheduleVOs.get(pkSchedule);
        List<ScheduleItemVO> scheduleItemVOS = scheduleTreeVO.get(null);
        //按code排序，是为了把修改的节点贴合在一起
        SortListUtil.sort(scheduleItemVOS,ScheduleItemVO.CODE,PlanVersionCompareConst.PLAN_CODE_ASC);

        scheduleVO.setScheduleItemVOS(scheduleItemVOS.toArray(new ScheduleItemVO[]{}));
        return scheduleVO;
    }

    /**
     * 对比处理
     * @param currentVersionVOs 当前版本节点集合
     * @param pkOriginals 当前版本和上个版本唯一位判断集合
     * @param currentVersionMap 当前版本节点容器,key：pkOriginal value：节点VO
     * @param preVersionMap 上个版本节点容器,key：pkOriginal value：节点VO
     * @param preVersionDeleteProcessMap 上个版本所有节点容器 key：pkScheduleItem, value : 节点VO
     * @throws BusinessException
     */
    private void compareProcess(List<ScheduleItemVO> currentVersionVOs, Set<String> pkOriginals, Map<String, ScheduleItemVO> currentVersionMap, Map<String, ScheduleItemVO> preVersionMap, Map<String, ScheduleItemVO> preVersionDeleteProcessMap) throws BusinessException {
        //用code全集分别和两个结果集的数据进行比较
        for(String pkOriginal : pkOriginals){
            ScheduleItemVO currentScheduleItemVO = currentVersionMap.get(pkOriginal);
            ScheduleItemVO preScheduleItemVO = preVersionMap.get(pkOriginal);

            if(null != currentScheduleItemVO && null != preScheduleItemVO){//当前版本和上一个版本都有（比较内容差异）
                contentDif(currentVersionVOs, currentScheduleItemVO, preScheduleItemVO);
            }else if(null != currentScheduleItemVO && null == preScheduleItemVO){//新增
                addDif(currentScheduleItemVO);
            }else if(null == currentScheduleItemVO && null != preScheduleItemVO){//删除（注：删除的节点不在当前树结构下，所以处理完当前节点标记后，需要额外把节点以及潜在被删除的上级节点都挂在当前树结构下边）
                delDif(currentVersionMap, preVersionDeleteProcessMap, preScheduleItemVO,currentVersionVOs);
            }
        }
    }

    /**
     * 新增节点直接打标
     * @param currentScheduleItemVO 当前版本处理新增节点
     */
    private void addDif(ScheduleItemVO currentScheduleItemVO) {
        //直接打标
        currentScheduleItemVO.setChangeRecordFlag(PlanVersionCompareConst.PLAN_VERSION_NODE_ADD);
    }

    /**
     * 内容比较
     * @param currentVersionVOs  当前版本所有节点
     * @param currentScheduleItemVO  当前处理节点
     * @param preScheduleItemVO  前一个版本节点（对应当前处理节点）
     * @throws BusinessException
     */
    private void contentDif(List<ScheduleItemVO> currentVersionVOs, ScheduleItemVO currentScheduleItemVO, ScheduleItemVO preScheduleItemVO) throws BusinessException {
        //内容比较
        List<String> changeField = getChangeField(currentScheduleItemVO, preScheduleItemVO);
        if(changeField == null || changeField.size() == 0){
            return;
        }
        //打修改标记
        currentScheduleItemVO.setChangeRecordFlag(PlanVersionCompareConst.PLAN_VERSION_NODE_UPDATE);
        //记录改动字段
        currentScheduleItemVO.setChangeRecords(changeField);

        //修改上个版本中的父节点为当前版本对应条目的节点
        if(null != currentScheduleItemVO.getPkParent()){
            preScheduleItemVO.setPkParent(currentScheduleItemVO.getPkParent());
        }
        preScheduleItemVO.setPkRoot(currentScheduleItemVO.getPkRoot());
        //将preScheduleItemVO添加到当前树节点下方（注：因当前节点只是展示用到，所以不展示当前节点的子节点）
        preScheduleItemVO.setChildren(null);
        //把上个版本对应的节点加入到当前版本的树结构中
        currentVersionVOs.add(preScheduleItemVO);
    }

    /**
     * 当前版本和上个版本所有容器初始化
     * @param scheduleItemVOSList 结果集(注：把数组中层叠级别的递归成平级节点的集合)
     * @param pkOriginals 当前版本和上个版本唯一位取值集合
     * @param versionMap 当前版本/上个版本节点容器 key：code, value : 节点VO
     * @param deleteMap 上个版本所有节点容器 key：pkScheduleItem, value : 节点VO (用途：处理删除节点用到,删除节点不在当前版本的树结构上)
     * @return
     */
    private ScheduleItemVO[] parseAllSubNodes(ScheduleItemVO[] scheduleItemVOS,List<ScheduleItemVO> scheduleItemVOSList,Set<String> pkOriginals,Map<String,ScheduleItemVO> versionMap,Map<String,ScheduleItemVO> deleteMap) {
        if(scheduleItemVOS == null || scheduleItemVOS.length == 0){
            return new ScheduleItemVO[0];
        }else{
            //表体有值
            for(ScheduleItemVO scheduleItemVO : scheduleItemVOS){
                pkOriginals.add(scheduleItemVO.getPkOriginal());
                scheduleItemVOSList.add(scheduleItemVO);
                versionMap.put(scheduleItemVO.getPkOriginal().trim(),scheduleItemVO);
                if(null == deleteMap){
                    //do nothing
                }else{
                    deleteMap.put(scheduleItemVO.getPkScheduleItem(),scheduleItemVO);
                }

                if(null == scheduleItemVO.getChildren() || scheduleItemVO.getChildren().length == 0){
                    continue;
                }else{
                    parseAllSubNodes(scheduleItemVO.getChildren(),scheduleItemVOSList,pkOriginals,versionMap,deleteMap);
                }
            }
        }
        return null;
    }

    private Map<String, List<ScheduleItemVO>> getScheduleTreeVO(List<ScheduleItemVO> itemVOS){
        Map<String, List<ScheduleItemVO>> itemMap = new HashMap<>();
        for(ScheduleItemVO itemVO : itemVOS){
            List<ScheduleItemVO> itemVOListTemp = itemMap.get(itemVO.getPkParent());

            if(itemVOListTemp == null){
                itemVOListTemp = new ArrayList<>();
                itemMap.put(itemVO.getPkParent(), itemVOListTemp);
            }

            itemVOListTemp.add(itemVO);
        }

        for(ScheduleItemVO scheduleItemVO : itemVOS){
            List<ScheduleItemVO> scheduleItemVOS = itemMap.get(scheduleItemVO.getPkScheduleItem());
            if(null != scheduleItemVOS && scheduleItemVOS.size() > 0){
                SortListUtil.sort(scheduleItemVOS,ScheduleItemVO.CODE,PlanVersionCompareConst.PLAN_CODE_ASC);
                scheduleItemVO.setChildren(itemMap.get(scheduleItemVO.getPkScheduleItem()).toArray(new ScheduleItemVO[]{}));
            }
        }

        return itemMap;
    }

    /**
     * 当前版本和上个版本对应节点内容比较
     * @param currentScheduleItemVO 当前版本节点
     * @param preScheduleItemVO 上个版本对应节点
     * @return 差异字段集合
     * @throws BusinessException
     */
    private List<String> getChangeField(ScheduleItemVO currentScheduleItemVO, ScheduleItemVO preScheduleItemVO) throws BusinessException {
        PlanVersion planVersion = CompareConfigurationUtil.getConfInfo();

        final List<Property> properties = planVersion.getProperties();

        Map<String,String> compareAttr = new HashMap<>();

        if(null == properties || properties.size() == 0){
            throw new BusinessException("读取配置文件错误");
        }
        for(Property property : properties){
            String propertyname = property.getPropertyname();
            String pagename = property.getPagename();
            compareAttr.put(propertyname,pagename);
        }

        Map<String,String> compareProgressAttr = new HashMap<>();
        Progress progress = planVersion.getProgress();
        if(null != progress){
            List<Property> progressProperties = progress.getProperties();
            for(Property property : progressProperties){
                String propertyname = property.getPropertyname();
                String pagename = property.getPagename();
                compareProgressAttr.put(propertyname,pagename);
            }
        }

        List<String> compareResult = compareFields(currentScheduleItemVO, preScheduleItemVO,compareAttr);
        List<String> compareResultProgress = compareFields(currentScheduleItemVO.getProgressVO(), preScheduleItemVO.getProgressVO(),compareProgressAttr);

        compareResult.addAll(compareResultProgress);

        return compareResult;
    }

    /**
     * @param obj1 进行属性比较的对象1
     * @param obj2 进行属性比较的对象2
     * @param compareAttr 选择需要比较的属性数组
     * @return 属性差异比较结果 map
     */
    private List<String> compareFields(Object obj1,Object obj2,Map<String,String> compareAttr){
        try {
            List<String> fields = new ArrayList<>();
            List<String> compareList = null;
            if (compareAttr !=null && compareAttr.keySet().size()>0) {
                compareList = new ArrayList<>(compareAttr.keySet());
            }
            if (obj1.getClass() == obj2.getClass()) { //只有两个对象都是同一类型才有可比性
                Class clazz = obj1.getClass();
                //获取object的属性描述
                PropertyDescriptor[] pds = Introspector.getBeanInfo(clazz, Object.class).getPropertyDescriptors();
                for (PropertyDescriptor pd : pds) {
                    String name = pd.getName();
                    if (compareList == null || !compareList.contains(name)) {
                        continue;
                    }
                    Method readMethod = pd.getReadMethod();//获取属性的get方法
                    //在obj1上调用get方法等同于获得obj1的属性值
                    Object o1 = readMethod.invoke(obj1);
                    //在obj2上调用get方法等同于获得obj2的属性值
                    Object o2 = readMethod.invoke(obj2);


                    //null值判断
                    if (o1 == null && o2 == null) {
                        continue;
                    }else if((o1 == null && o2 != null) || (o1 != null && o2 == null)){
                        fields.add(compareAttr.get(name));
                        continue;
                    }

                    //日期单独判断
                    if(o1 instanceof Date && o2 instanceof Date){
                        if(!DateUtils.isSameDay((Date)o1,(Date)o2)){
                            fields.add(compareAttr.get(name));
                        }
                    }else if(o1 instanceof String && o2 instanceof String){
                        if (!o1.equals(o2)) {
                            fields.add(compareAttr.get(name));
                        }
                    }else if(o1 instanceof Integer && o2 instanceof Integer){
                        if (!o1.equals(o2)) {
                            fields.add(compareAttr.get(name));
                        }
                    }else{
                        //其他类型根据需要添加...
                    }

                }
            }
            return fields;
        } catch (Exception e) {
            new BusinessException("对比属性有误");
        }
        return new ArrayList<>();
    }

    /**
     * 删除节点打标,递归向上查找，找到从哪个节点删除以后,更改找到条目数据的父节点pk_parent为当前版本的对应树节点上,完成树拼接
     * @param currentVersionMap
     * @param preVersionDeleteProcessMap
     * @param preScheduleItemVO
     * @param currentVersionVO
     */
    private void delDif(Map<String, ScheduleItemVO> currentVersionMap, Map<String, ScheduleItemVO> preVersionDeleteProcessMap, ScheduleItemVO preScheduleItemVO, List<ScheduleItemVO> currentVersionVO) {

        String pkParent = preScheduleItemVO.getPkParent();

        //在准备好的父节点集合中找到上级节点
        ScheduleItemVO deleteParentNode = preVersionDeleteProcessMap.get(pkParent);
        if(null != deleteParentNode){
            ScheduleItemVO currentVersionParentNode = currentVersionMap.get(deleteParentNode.getPkOriginal());
            if(null != currentVersionParentNode){//出口 说明是在这个节点的子节点进行删除的
                //把当前节点挂到指定版本的树结构上,只放一次
                if(pkParent.equals(currentVersionParentNode.getPkScheduleItem())){//当前删除节点的值父节点等于当前版本父节点的值    说明之前已经重置过这个值了
                    return;
                }else{//在这里重置父节点和根节点
                    //打删除标记
                    preScheduleItemVO.setChangeRecordFlag(PlanVersionCompareConst.PLAN_VERSION_NODE_DELETE);

                    //添加到现有的树结构里
                    preScheduleItemVO.setPkParent(currentVersionParentNode.getPkScheduleItem());
                    preScheduleItemVO.setPkRoot(currentVersionParentNode.getPkRoot());
                    addDifContent(preScheduleItemVO, currentVersionVO);
                }
            }else{
                preScheduleItemVO.setChangeRecordFlag(PlanVersionCompareConst.PLAN_VERSION_NODE_DELETE);
                addDifContent(preScheduleItemVO, currentVersionVO);
                //没找到继续向上查找
                delDif(currentVersionMap,preVersionDeleteProcessMap,deleteParentNode,currentVersionVO);
            }
        }else{
            if(null == currentVersionMap.get(preScheduleItemVO.getPkOriginal())){
                //打删除标记
                preScheduleItemVO.setChangeRecordFlag(PlanVersionCompareConst.PLAN_VERSION_NODE_DELETE);
                //根节点
                addDifContent(preScheduleItemVO, currentVersionVO);
            }else{
                //do nothing
            }
        }
    }

    /**
     * 添加差异增量
     * @param preScheduleItemVO
     * @param tempDeleteVOs
     */
    private void addDifContent(ScheduleItemVO preScheduleItemVO, List<ScheduleItemVO> tempDeleteVOs) {
        boolean isAdd = true;
        for(ScheduleItemVO scheduleItemVO : tempDeleteVOs){
            if(scheduleItemVO.getPkScheduleItem().equals(preScheduleItemVO.getPkScheduleItem())){//已经添加过就不再添加了
                return;
            }
        }
        if(isAdd){
            tempDeleteVOs.add(preScheduleItemVO);
        }
    }
}
